package asia.hymsk.spring.utils;
/**
 * @author 花于陌上开
 * @date 2023-06-25 16:08
 */
public class SnowUtils {
        private final long twepoch = 1288834974657L;
        private final long workerIdBits = 5L;
        private final long datacenterIdBits = 5L;
        private final long maxWorkerId = ~(-1L << workerIdBits);
        private final long maxDatacenterId = ~(-1L << datacenterIdBits);
        private final long sequenceBits = 12L;
        private final long workerIdShift = sequenceBits;
        private final long datacenterIdShift = sequenceBits + workerIdBits;
        private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
        private final long sequenceMask = ~(-1L << sequenceBits);
        private long sequence = 0L;
        private final long workerId;
        private final long datacenterId;
        private long lastTimestamp = -1L;

        private SnowUtils(long workerId, long datacenterId)
        {
            if (workerId > maxWorkerId || workerId < 0) {
                throw new IllegalArgumentException(String.format("workerId can't be greater than %d or less than 0", maxWorkerId));
            }

            if (datacenterId > maxDatacenterId || datacenterId < 0) {
                throw new IllegalArgumentException(String.format("datacenterId can't be greater than %d or less than 0", maxDatacenterId));
            }

            this.workerId = workerId;
            this.datacenterId = datacenterId;
        }

        public static SnowUtils getInstance(long workerId, long datacenterId)
        {
            return new SnowUtils(workerId, datacenterId);
        }

        public synchronized long nextId() {
            long timestamp = timeGen();

            if (timestamp < lastTimestamp) {
                throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
            }

            if (lastTimestamp == timestamp) {
                sequence = (sequence + 1) & sequenceMask;
                if (sequence == 0) {
                    timestamp = tilNextMillis(lastTimestamp);
                }
            } else {
                sequence = 0L;
            }

            lastTimestamp = timestamp;

            return ((timestamp - twepoch) << timestampLeftShift) |
                    (datacenterId << datacenterIdShift) |
                    (workerId << workerIdShift) |
                    sequence;
        }

        private long tilNextMillis(long lastTimestamp) {
            long timestamp = timeGen();
            while (timestamp <= lastTimestamp) {
                timestamp = timeGen();
            }
            return timestamp;
        }

        private long timeGen() {
            return System.currentTimeMillis();
        }
}
